package il.ac.technion.wiki.impl.storage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Pattern;

import il.ac.technion.wiki.api.IDataStore;
import il.ac.technion.wiki.api.IWikiEngine;
import il.ac.technion.wiki.api.IWikiEngine.IWikiContent;
import il.ac.technion.wiki.api.search.AndSearchExpression;
import il.ac.technion.wiki.api.search.ISearchExpression;
import il.ac.technion.wiki.api.search.NotSearchExpression;
import il.ac.technion.wiki.api.search.OrSearchExpression;
import il.ac.technion.wiki.api.search.TextSearchExpression;

public final class SimpleDataStorage implements IDataStore {
	private Class<? extends IWikiContent> contentClass;
	private Map<String, String> nameToIdMap  = new HashMap<String, String>();
	private Map<String, ArrayList<IWikiContent> > data = new HashMap<String, ArrayList<IWikiContent> >();
	//private AbstractWikiEngine wikiEngine = null;
	private Set<String> neutralWords = new HashSet<String>();
	
	public SimpleDataStorage() {		
	}
	
	private String genNewID(){
		Random rnd = new Random();
		String key = null;
		do {
			key = "id" + rnd.nextInt();
		}while (data.containsKey(key));
		return key;
	}
	
	@Override
	public void setContentClass(Class<? extends IWikiContent> clazz) {
		this.contentClass = clazz;
	}

	@Override
	public void setWikiEngine(IWikiEngine wikiEngine) {
		//this.wikiEngine = wikiEngine;
	}

	@Override
	public void setNeutralWords(String[] words) {
		for (String word: words){
			neutralWords.add(word);
		}
	}

	@Override
	public IWikiContent get(String name, int revision)
			throws NotFoundException, DataStoreException {
		if (0 > revision)
			throw new DataStoreException();
		if (!nameToIdMap.containsKey(name))
			throw new NotFoundException();
		String id = nameToIdMap.get(name);
		if (data.get(id).size() <= revision)
			throw new DataStoreException();
		return data.get(id).get(revision);
	}

	@Override
	public void put(IWikiContent content, Boolean insert)
			throws NotFoundException, NameExistsException, DataStoreException {
		if (insert){
			insert(content);
			return;
		}
		replace(content);
	}
	
	private void insert(IWikiContent content) throws NameExistsException, DataStoreException{
		if (content.getRevision() != 0)
			throw new DataStoreException();
		if (nameToIdMap.containsKey(content.getName()))
			throw new NameExistsException();		
		ArrayList<IWikiContent> contenctArray = new ArrayList<IWikiContent>();
		contenctArray.add(content);
		String id = genNewID();
		nameToIdMap.put(content.getName(), id);
		data.put(id, contenctArray);
	}
	
	private void replace(IWikiContent content) throws DataStoreException{
		if (0 > content.getRevision())
			throw new DataStoreException();
		if (!nameToIdMap.containsKey(content.getName()))
			throw new NotFoundException();
		ArrayList<IWikiContent> contenctArray = data.get(nameToIdMap.get(content.getName()));
		if (contenctArray.size() < content.getRevision())
			throw new DataStoreException();
		while(contenctArray.size() > content.getRevision()){
			contenctArray.remove(contenctArray.size() - 1);
		}
		if (contenctArray.size() == content.getRevision()){
			contenctArray.add(content.getRevision(), contentClass.cast(content));
		}
	}

	@Override
	public void delete(String name) throws NotFoundException,
			DataStoreException {
		if (!nameToIdMap.containsKey(name))
			throw new NotFoundException();
		String id = nameToIdMap.get(name);
		nameToIdMap.remove(name);
		data.remove(id);
	}

	@Override
	public void rename(String name, String newName) throws NotFoundException,
			NameExistsException, DataStoreException {
		if (!nameToIdMap.containsKey(name))
			throw new NotFoundException();
		if (name == newName)
			return;
		if (nameToIdMap.containsKey(newName))
			throw new NameExistsException();
		String id = nameToIdMap.get(name);
		nameToIdMap.remove(name);
		nameToIdMap.put(newName, id);
	}
	
	@Override
	public IWikiContent[] search(final ISearchExpression expr, final int limit)
			throws DataStoreException {
		QueryType qt = null;
		for (QueryType q: QueryType.values()){
			if (q.canParseQuery(expr)){
				qt = q;
				break;
			}
		}
		if (qt == null)
			throw new RuntimeException();
		ArrayList<IWikiContent> results = new ArrayList<IWikiContent>();
		Iterator<ArrayList<IWikiContent>> it = data.values().iterator();
		while((limit < 1 || results.size() < limit) && it.hasNext()){
			ArrayList<IWikiContent> currentContent = it.next();
			if (currentContent.size() == 0)
				continue;
			if(qt.evaluate(expr, currentContent, neutralWords)){
				results.add(currentContent.get(currentContent.size() - 1));
			}
		}
		return results.toArray(new IWikiContent[0]);
	}
	
	public enum QueryType{
		AND(AndSearchExpression.class) {
			
			@Override
			boolean evaluate(ISearchExpression e, ArrayList<IWikiContent> contentArr, Set<String> neutralWords){
				AndSearchExpression andExp = (AndSearchExpression)e;
				QueryType left = null,right = null;
				for (QueryType q: QueryType.values()){
					if (q.canParseQuery(andExp.left))
						left = q;
					if (q.canParseQuery(andExp.right))
						right = q; 
				}
				if (left == null || right == null)
					throw new RuntimeException();
				return left.evaluate(andExp.left, contentArr, neutralWords) && right.evaluate(andExp.right, contentArr, neutralWords);
			}						
		},
		OR(OrSearchExpression.class) {
			@Override
			boolean evaluate(ISearchExpression e, ArrayList<IWikiContent> contentArr, Set<String> neutralWords){
				OrSearchExpression orExp = (OrSearchExpression)e;
				QueryType left = null,right = null;
				for (QueryType q: QueryType.values()){
					if (q.canParseQuery(orExp.left))
						left = q;
					if (q.canParseQuery(orExp.right))
						right = q; 
				}
				if (left == null || right == null)
					throw new RuntimeException();
				return left.evaluate(orExp.left, contentArr, neutralWords) || right.evaluate(orExp.right, contentArr, neutralWords);
			}
		},
		NOT(NotSearchExpression.class) {
			@Override
			boolean evaluate(ISearchExpression e, ArrayList<IWikiContent> contentArr, Set<String> neutralWords){
				NotSearchExpression notExp = (NotSearchExpression)e;				
				for (QueryType q: QueryType.values()){
					if (q.canParseQuery(notExp.exp))
						return !q.evaluate(notExp.exp, contentArr, neutralWords);
				}
				throw new RuntimeException();
			}
		},
		EXPRESSION(TextSearchExpression.class) {			
			String removeNeutralWords(String text, Set<String> neutralWords){				
				String res = text;
				for (String word : neutralWords){
					res = res.replaceAll(Pattern.quote(word), "");
				}
				return res;	
			}
			
			String stripWhiteSpaces(String str){
				return str.replaceAll("\\s+", " ");
			}
			
			@Override
			boolean evaluate(ISearchExpression e, ArrayList<IWikiContent> contentArr, Set<String> neutralWords){				
				return stripWhiteSpaces(removeNeutralWords(contentArr.get(contentArr.size() - 1).getText().toLowerCase(), neutralWords)).contains(stripWhiteSpaces(removeNeutralWords(((TextSearchExpression)e).text, neutralWords)));
			}
		};		
		
		Class<? extends ISearchExpression> c;
		QueryType(Class<? extends ISearchExpression> c){
			this.c = c; 
		}
		
		abstract boolean evaluate(ISearchExpression e, ArrayList<IWikiContent> contentArr, Set<String> neutralWords);
		
		boolean canParseQuery(ISearchExpression e) {
			return e.getClass().equals(this.c);
		}
		
		
	}
	
}
